using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text.RegularExpressions;

namespace System.Windows.Forms
{
    #region Enumerations

    /// <summary>
    /// Different shapes
    /// </summary>
    public enum ShapeStyle
    {
        Ellipse,
        RoundedRectangle,
        //Balloon,
        Star,
        Triangle,
        Rectangle,
        Pentagon,
        Hexagon,
        Septagon,
        Octagon,
        Arrow,
        LightningBolt,
        Arc,
        RightTriangle,
        UserDefined,
    }

    #endregion Enumerations

    /// <summary>
    /// Static provider of shapes based on a one unit square centered on the origin.
    /// </summary>
    public static class ShapePathProvider
    {
        public const float r = 0.5f;

        private static Regex pathParser;
        private static Dictionary<string, GraphicsPath> shapeCache;

        static ShapePathProvider()
        {
            shapeCache = new Dictionary<string, GraphicsPath>(5);
        }

        /// <summary>
        /// Gets a path for the specified shape based on a one unit square centered on the origin.
        /// </summary>
        /// <param name="shape">Shape to get path for</param>
        /// <returns>GraphicsPath with coordinates of shape</returns>
        public static GraphicsPath GetShapePath(ShapeStyle shape)
        {
            GraphicsPath path;
            if (shapeCache.TryGetValue(shape.ToString(), out path))
                return path;

            path = new GraphicsPath();
            Matrix matrix = new Matrix();
            switch (shape)
            {
                case ShapeStyle.Rectangle:
                    path.AddRectangle(new RectangleF(-r,-r,2*r,2*r));
                    break;
                case ShapeStyle.Ellipse:
                    path.AddEllipse(-r,-r,2*r,2*r);
                    break;
                case ShapeStyle.RoundedRectangle:
                    // Add four corner arcs
                    float w = r/2;
                    RectangleF arcr = new RectangleF(-r,-r,w,w);
                    path.AddArc(arcr, 180, 90);
                    arcr.Offset(1 - w, 0);
                    path.AddArc(arcr, 270, 90);
                    arcr.Offset(0, 1 - w);
                    path.AddArc(arcr, 0, 90);
                    arcr.Offset(w - 1, 0);
                    path.AddArc(arcr, 90, 90);
                    break;
                /*case ShapeStyle.Balloon:
                    path.AddArc(0, 0, width / 4, width / 4, 180, 90);
                    path.AddLine(width / 8, 0, width - width / 8, 0);
                    path.AddArc(width - width / 4, 0, width / 4, width / 4, 270, 90);
                    path.AddLine(width, width / 8, width, (height * 0.75f) - width / 8);
                    path.AddArc(width - width / 4, (height * 0.75f) - width / 4, width / 4, width / 4, 0, 90);
                    path.AddLine(width - width / 8, (height * 0.75f), width / 8 + (width / 4), (height * 0.75f));
                    path.AddLine(width / 8 + (width / 4), height * 0.75f, width / 8 + (width / 8), height);
                    path.AddLine(width / 8 + (width / 8), height, width / 8 + (width / 8), (height * 0.75f));
                    path.AddLine(width / 8 + (width / 8), (height * 0.75f), width / 8, (height * 0.75f));
                    path.AddArc(0, (height * 0.75f) - width / 4, width / 4, width / 4, 90, 90);
                    path.AddLine(0, (height * 0.75f) - width / 8, 0, width / 8);
                    break;*/
                case ShapeStyle.Star:
                    // Inner radian (r2) = (1 - (sin(2pi/5)/cos(pi/10))) * r
                    double r2 = (1 - (System.Math.Sin(0.2*System.Math.PI)/System.Math.Cos(0.1*System.Math.PI))) * r;
                    PointF[] outerPts = GetPolygonPoints(5, r, -0.1f);			// Outer pentagon rotated up 18'
                    PointF[] innerPts = GetPolygonPoints(5, (float)r2, 0.1f);	// Inner pentagon rotated down 18'
                    PointF[] pts = new PointF[10];
                    for (int i = 0; i < 5; i++)
                    {
                        pts[2*i] = outerPts[i];
                        pts[(2*i)+1] = innerPts[i];
                    }
                    path.AddLines(pts);
                    break;
                case ShapeStyle.Arrow:
                    path.AddLines(new PointF[] {
                        new PointF(0, 0.5f), new PointF(0.5f, 0), new PointF(0,-0.5f),
                        new PointF(0, -0.25f), new PointF(-0.5f, -0.25f), new PointF(-0.5f, 0.25f),
                        new PointF(0, 0.25f) });
                    break;
                case ShapeStyle.LightningBolt:
                    path.AddLines(new PointF[] {
                        new PointF(-0.1f, -0.5f), new PointF(0.1f, -.22f), new PointF(0.01f, -0.18f),
                        new PointF(0.27f, 0.05f), new PointF(0.19f, 0.1f), new PointF(0.5f, 0.5f),
                        new PointF(-0.03f, 0.19f), new PointF(0.07f, 0.14f), new PointF(-0.27f, -0.05f),
                        new PointF(-0.15f, -0.11f), new PointF(-0.5f, -0.32f) });
                    break;
                case ShapeStyle.Arc:
                    path.AddArc(-r, -r, 4*r, 4*r, 180, 90);
                    path.AddLine(-r, -r, -r, r);
                    break;
                case ShapeStyle.RightTriangle:
                    path.AddLines(new PointF[] {
                        new PointF(-r, -r), new PointF(r, -r), new PointF(-r, r) });
                    break;
                case ShapeStyle.UserDefined:
                    throw new NotImplementedException("User defined styles are not yet implemented");
                default:
                    int sides = (int)shape;
                    // Rotate so odd verticed polygons have point up and evens have flat top
                    float radrot = (1 == sides % 2) ? -0.5f /*-90*/: (1f/sides)-0.5f;//90f-(180f/(float)sides));
                    path.AddLines(GetPolygonPoints(sides, r, radrot));
                    break;
            }
            path.CloseFigure();
            if (shape != ShapeStyle.UserDefined)
                shapeCache.Add(shape.ToString(), path);
            return path;
        }

        public static GraphicsPath GetShapePath(string name, string pathMarkup)
        {
            GraphicsPath path;
            if (shapeCache.TryGetValue(name, out path))
                return path;

            if (pathParser == null)
                pathParser = new Regex(@"(?:(?<FillRule>F0|F1)\s+)?M\s+(?<startX>\d+(?:\.\d+)?)\s*,\s*(?<startY>\d+(?:\.\d+)?)(?:\s+(?<elem>(?<line>L(?:\s+\s*(?<lineX>\d+(?:\.\d+)?),\s*(?<lineY>\d+(?:\.\d+)?))+)|(?<arc>A\s+(?<arcW>\d+)\s*,\s*(?<arcH>\d+)\s+(?<arcRot>\d+(?:\.\d+)?)\s+(?<arcLrg>[01])\s+(?<arcPos>[01])\s+(?<arcEndX>\d+(?:\.\d+)?)\s*,\s*(?<arcEndY>\d+(?:\.\d+)?))))+(?:\s+Z)?", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline);
            path = new GraphicsPath();
            Match m = pathParser.Match(pathMarkup);
            if (!m.Success)
                throw new ArgumentException("Invalid path markup syntax.", "pathMarkup");
            PointF start = new PointF(float.Parse(m.Groups["startX"].Value), float.Parse(m.Groups["startY"].Value));
            int l = 0, a = 0;
            for (int e = 0; e < m.Groups["elem"].Captures.Count; e++)
            {
                Capture c = m.Groups["elem"].Captures[e];
                switch (c.Value[0])
                {
                    case 'A':
                    case 'a':
                        float w = float.Parse(m.Groups["arcW"].Captures[a].Value);
                        float h = float.Parse(m.Groups["arcH"].Captures[a].Value);
                        float ar = float.Parse(m.Groups["arcRot"].Captures[a].Value);
                        path.AddArc(start.X, start.Y, w, h, 0, ar);
                        start = new PointF(float.Parse(m.Groups["arcEndX"].Captures[a].Value), float.Parse(m.Groups["arcEndY"].Captures[a].Value));
                        a++;
                        break;
                    case 'L':
                    case 'l':
                        int elemEnd = c.Index + c.Length - 1;
                        while (l < m.Groups["lineX"].Captures.Count && m.Groups["lineX"].Captures[l].Index < elemEnd)
                        {
                            PointF pt = new PointF(float.Parse(m.Groups["lineX"].Captures[l].Value), float.Parse(m.Groups["lineY"].Captures[l].Value));
                            path.AddLine(start, pt);
                            start = pt;
                            l++;
                        }
                        break;
                    default:
                        break;
                }
            }

            shapeCache.Add(name, path);
            return path;
        }

        private static PointF[] GetPolygonPoints(int sides, float circumradius)
        {
            return GetPolygonPoints(sides, circumradius, 0);
        }

        private static PointF[] GetPolygonPoints(int sides, float circumradius, float radianRotation)
        {
            PointF[] pts = new PointF[sides];
            double radians;
            for (int i = 0; i < sides; i++)
            {
                radians = (System.Math.PI*((2*i) + (sides*radianRotation)))/sides;
                pts[i] = new PointF((float)(System.Math.Cos(radians)*circumradius), (float)(System.Math.Sin(radians)*circumradius));
            }
            return pts;
        }
    }
}